﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Reflection;
using System.Threading;

namespace TWiStEr.ConsoleHighlighter {
	class Colors {
		public ConsoleColor Foreground;
		public ConsoleColor? Background;
	}
	class Program {
		static Dictionary<Regex, Colors[]> colorSettings = new Dictionary<Regex, Colors[]>();
		static Colors Default;
		static void Main(string[] args) {
			Default = new Colors { Foreground = Console.ForegroundColor, Background = Console.BackgroundColor };
			string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			string patternsFileName = Path.Combine(dir, @"Patterns.set");
			string colorsFileName = Path.Combine(dir, @"Colors.set");
			ReadConfig(patternsFileName, colorsFileName);

			string line = null;
			while ((line = Console.ReadLine()) != null) {
				bool handled = false;
				foreach (Regex r in colorSettings.Keys) {
					Match m = r.Match(line);
					if (m.Success) {
						string colored = Color(m, colorSettings[r]);
						if (colored != null) Console.WriteLine(colored);
						handled = true;
						break;
					}
				}
				if (!handled)
					Console.WriteLine(line);
				Console.Out.Flush();
			}
			Console.ForegroundColor = Default.Foreground;
			if (Default.Background.HasValue) Console.BackgroundColor = Default.Background.Value;
		}

		private static string reset = "\x001B[m";
		private static string Color(Match m, Colors[] colors) {
			StringBuilder sb = new StringBuilder();
			if (colors.Length == 0) return null;
			Colors whole = colors.get(0, Default);
			sb.Append(Colors(whole));
			Group lastGroup = m.Groups[0];
			string line = lastGroup.Value;
			if (m.Groups.Count > 1) {
				for (int i = 1; i < m.Groups.Count; i++) {
					Group g = m.Groups[i];
					if (g.Success) {
						int start = lastGroup.Index + (i == 1 ? 0 : lastGroup.Length);
						sb.Append(line.Substring(start, g.Index - start));
						sb.Append(Colors(colors.get(i, whole)));
						sb.Append(g.Value);
						sb.Append(Colors(whole));
						lastGroup = g;
					}
				}
				sb.Append(line.Substring(lastGroup.Index + lastGroup.Length));
			} else {
				sb.Append(line);
			}
			sb.Append(reset);
			return sb.ToString();
		}

		static ConsoleColor[] colorMap = { 
              ConsoleColor.Black,
              ConsoleColor.Red,
              ConsoleColor.Green,
              ConsoleColor.Yellow,
              ConsoleColor.Blue,
              ConsoleColor.Magenta,
              ConsoleColor.Cyan, 
              ConsoleColor.White };
		static ConsoleColor[] colorMapDark = { 
              ConsoleColor.Black,
              ConsoleColor.DarkRed,
              ConsoleColor.DarkGreen,
              ConsoleColor.DarkYellow,
              ConsoleColor.DarkBlue,
              ConsoleColor.DarkMagenta,
              ConsoleColor.DarkCyan, 
              ConsoleColor.Gray };
		private static string Colors(Colors colors) {
			return Color(colors.Foreground, true) + (colors.Background.HasValue ? Color(colors.Background.Value, false) : "");
		}
		private static string Color(ConsoleColor consoleColor, bool fg) {
			int dim = 1;
			int color = 7;
			for (int i = 0; i < colorMap.Length; i++) {
				if (colorMap[i] == consoleColor) {
					color = i;
					dim = 1;
				}
			}
			for (int i = 0; i < colorMapDark.Length; i++) {
				if (colorMapDark[i] == consoleColor) {
					color = i;
					dim = 2;
				}
			}
			return string.Format("\x001B[{0};{2}{1}m", dim, color, fg ? 3 : 4);
		}

		private static void ReadConfig(string patternsFileName, string colorsFileName) {
			Dictionary<string, string> colors = getColors(colorsFileName);
			Func<string, string> getLast = col => {
				while (true) {
					try { col = colors[col]; } catch (KeyNotFoundException) { break; }
				}
				return col;
			};
			Func<string, ConsoleColor> toColor = col => {
				try {
					return (ConsoleColor)Enum.Parse(typeof(ConsoleColor), col);
				} catch (ArgumentException) {
					throw new ApplicationException("Color named: " + col + " not found.");
				}
			};
			Func<string, Colors> toColors = col => {
				col = getLast(col);
				string[] cols = col.Split('|');
				if (cols.Length == 1) {
					return new Colors { Foreground = toColor(cols[0]) };
				} else if (cols.Length == 2) {
					return new Colors { Foreground = toColor(cols[0]), Background = toColor(cols[1]) };
				} else
					throw new ApplicationException("Invaild Background format usage: \"" + col + "\", valid is 'fgCol|bgCol'");
			};
			string[] configs = File.ReadAllLines(patternsFileName);
			foreach (string config in configs) {
				string[] parts = config.Split('\t');
				if (string.IsNullOrEmpty(parts[0].Trim())) continue;
				Regex pattern = new Regex(parts[0], RegexOptions.IgnoreCase | RegexOptions.Singleline);
				colorSettings.Add(pattern, parts.Skip(1).Select(toColors).ToArray());
			}
		}

		private static Dictionary<string, string> getColors(string colorsFileName) {
			Dictionary<string, string> colorMap = new Dictionary<string, string>();
			colorMap["Default"] = Default.Foreground.ToString() + "|" + Default.Background.ToString();
			string[] colors = File.ReadAllLines(colorsFileName);
			foreach (string color in colors) {
				string[] parts = color.Split('=');
				if (parts.Length != 2) continue;
				string key = parts[0].Trim();
				string value = parts[1].Trim();
				if (key.Length == 0 || value.Length == 0)
					throw new ApplicationException("Invalid Color mapping: \"" + color +
						"\", it must be 'Mapping=Color' or 'Mapping=Mapping', white space is skipped");
				colorMap.Add(key, value);
			}
			return colorMap;
		}
	}
	static class Ext {
		public static T get<T>(this T[] array, int index, T @default) {
			return index < array.Length ? array[index] : @default;
		}
	}
}
